﻿using System;
using System.Collections.Generic;
using System.Linq;

using System.Data.Objects.DataClasses;
using Pharmacy.Data;
using System.Reflection;
using System.Transactions;
using Pharmacy.Data.Entity;
using Pharmacy.Ultilities.Resources;
using Pharmacy.Ultilities;

namespace Pharmacy.Data.Core
{
    /// <summary>
    /// </summary>
    public class PessimisticLocking : ILockingControl
    {
        private const long LOCKINGINTERVAL = 86400;
        private const string KEY_LOCKINGINTERVAL = "PessimisticLockingInterval";
        private readonly string lockID = Guid.NewGuid().ToString();
        private readonly Lazy<IObjectContext> _pharmacyContextLazy;
        private readonly Lazy<Repository<LockTable>> _rMerchantLockLazy;
        private readonly List<string> listLockedTableKeyMerchant = new List<string>();
        private readonly Lazy<long> lockingIntervalMerchant;

        public PessimisticLocking(IObjectContext merchantContext)
        {
            _pharmacyContextLazy = new Lazy<IObjectContext>(() => { return merchantContext.CreateNew(); });


            _rMerchantLockLazy = new Lazy<Repository<LockTable>>(() =>
            {
                return new Repository<LockTable>(_pharmacyContextLazy.Value, MergeOption.Overrive);
            });
            //TODO : Cache lockingIntervalMaster,lockingIntervalMerchant (static var)
            lockingIntervalMerchant = new Lazy<long>(() =>
            {
                Repository<CommonConfig> rconfig = new Repository<CommonConfig>(_pharmacyContextLazy.Value);
                var config = rconfig.FirstOrDefault(m => m.Key == KEY_LOCKINGINTERVAL);
                if (config == null) return LOCKINGINTERVAL;
                else return long.Parse(config.Value);
            });
        }

        private long GetLockingInterval()
        {
            return lockingIntervalMerchant.Value;
        }
        /// <summary>
        /// Release Context of LockControl
        /// </summary>
        public void Dispose()
        {
            if (_pharmacyContextLazy.IsValueCreated)
                _pharmacyContextLazy.Value.Dispose();
        }

        public void UnLock(EntityObject entity)
        {
            List<EntityObject> list = new List<EntityObject>() { entity };
            UnLock(list);
        }
        public void UnLock(IEnumerable<EntityObject> listEntity)
        {
            if (listEntity == null) throw new ArgumentNullException("listEntity");
            if (listEntity.Count() == 0) return;
            var listLockedTableKey = listLockedTableKeyMerchant;
            if (listEntity.Where(m => !listLockedTableKey.Contains(GetEntityInfo(m).TableNameKey)).Count() > 0) throw new ArgumentException("listEntity Some object not in currrent lock");

            var listTableKeyForUnlock = listEntity.Select(m => GetEntityInfo(m).TableNameKey);

            using (TransactionScope tranScope = new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                SetUnlock(listTableKeyForUnlock);
                tranScope.Complete();
            }
        }
        public void Lock(EntityObject entity)
        {
            List<EntityObject> list = new List<EntityObject>() { entity };
            Lock(list);
        }
        public void Lock(IEnumerable<EntityObject> listEntity)
        {
            if (listEntity == null) throw new ArgumentNullException("listEntity");
            if (listEntity.Count() == 0) throw new ArgumentNullException("listEntity");
            var listLockedTableKey = listLockedTableKeyMerchant;
            IEnumerable<EntityObject> listToCheck = listEntity.Where(m => !listLockedTableKey.Contains(GetEntityInfo(m).TableNameKey));

            using (TransactionScope tranScope = new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                SetLock(listToCheck);
                tranScope.Complete();
            }

            listLockedTableKey.AddRange(listToCheck.Select(m => GetEntityInfo(m).TableNameKey));
        }

        public void UnLockAll()
        {
            using (TransactionScope tranScope = new TransactionScope(TransactionScopeOption.RequiresNew))
            {
                SetUnlock(listLockedTableKeyMerchant);
                tranScope.Complete();
            }
        }

        private void SetUnlock(IEnumerable<string> listTableKeyForUnlock)
        {
            if (listTableKeyForUnlock == null) throw new ArgumentNullException("listEntity");
            if (listTableKeyForUnlock.Count() == 0) return;
            var listLockedTableKey = listLockedTableKeyMerchant;
            DateTime CurrentDateTime = DateTime.Now;
            _rMerchantLockLazy.Value.AsQueryable().Where(m => listTableKeyForUnlock.Contains(m.TableNameKeyValue) && m.LockID == lockID).ToList().ForEach(m =>
            {
                _rMerchantLockLazy.Value.Delete(m);
            });
            _pharmacyContextLazy.Value.SaveChanges();
            listTableKeyForUnlock.ToList().ForEach(m =>
            {
                listLockedTableKey.Remove(m);
            });
        }

        private void SetLock(IEnumerable<EntityObject> listEntity)
        {
            if (listEntity == null) throw new ArgumentNullException("listEntity");
            if (listEntity.Count() == 0) return;
            var listEntityInfo = listEntity.Select(m => GetEntityInfo(m));
            var listTableKeyValue = listEntityInfo.Select(m => m.TableNameKey).ToArray();

            DateTime CurrentDateTime = _pharmacyContextLazy.Value.GetCurrentDateTime();
            var listLockInfo = _rMerchantLockLazy.Value.AsQueryable().Where(m => listTableKeyValue.Contains(m.TableNameKeyValue))
                .ToList()
                .Where(
                    m => (TimeSpan.FromTicks(CurrentDateTime.Ticks).TotalSeconds - m.TimeLock) < GetLockingInterval()
                )
                .Select(m =>
                    new LockingInfo()
                    {
                        ComputerName = m.ComputerName,
                        UserName = m.UserUpdated,
                        TableName = m.TableName,
                        Key = m.KeyValue,
                        StartLockTime = CurrentDateTime.AddSeconds(-(TimeSpan.FromTicks(CurrentDateTime.Ticks).TotalSeconds - m.TimeLock)),
                        EndLockTime = CurrentDateTime.AddSeconds(-(TimeSpan.FromTicks(CurrentDateTime.Ticks).TotalSeconds - m.TimeLock)).AddSeconds(GetLockingInterval())
                    });
            if (listLockInfo.Count() > 0)
            {
                throw new PessimisticLockingException("Object is locked by other session, detail in lockdata", listLockInfo);
            }
            //Insert to Lock Table
            listEntity.ToList().ForEach((EntityObject entity) =>
            {
                var enInfo = GetEntityInfo(entity);
                LockTable lockItem = new LockTable();
                lockItem.ComputerName = Environment.MachineName;
                lockItem.UserUpdated = _pharmacyContextLazy.Value.UserName;
                lockItem.UserCreated = _pharmacyContextLazy.Value.UserName;
                lockItem.TableName = enInfo.TableName;
                lockItem.TableNameKeyValue = enInfo.TableNameKey;
                lockItem.KeyValue = enInfo.Key;
                lockItem.LockID = lockID;
                lockItem.TimeLock = ((long)(TimeSpan.FromTicks(CurrentDateTime.Ticks).TotalSeconds) + GetLockingInterval());
                _rMerchantLockLazy.Value.Add(lockItem);
            });
            _pharmacyContextLazy.Value.SaveChanges();
        }
        /// <summary>
        /// Get Key For DiOMS - Only One Key Int
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        private EntityInfo GetEntityInfo(EntityObject entity)
        {
            Type entityType = entity.GetType();

            PropertyInfo[] propertyInfos = entityType.GetProperties();

            PropertyInfo keyProperty = null;

            foreach (var pInfo in propertyInfos)
            {
                var customAtt = pInfo.GetCustomAttributes(false);
                foreach (var att in customAtt)
                {
                    EdmScalarPropertyAttribute edm = att as EdmScalarPropertyAttribute;
                    if (edm != null && edm.EntityKeyProperty)
                    {
                        keyProperty = pInfo;
                        break;
                    }
                }
                if (keyProperty != null) break;
            }

            if (keyProperty == null) throw new ArgumentNullException("Key not found");


            string tableName = entity.GetType().Name;
            string key = keyProperty.GetValue(entity, null).ToString();
            string tableKeyName = tableName + key.ToString();
            return new EntityInfo() { Key = key, TableName = tableName, TableNameKey = tableKeyName };
        }

        public string IsCheckOutByAnotherUser(EntityObject entity)
        {
            string messageError = string.Empty;
            EntityInfo entityInfo = GetEntityInfo(entity);
            string tableKeyValue = entityInfo.TableNameKey;

            DateTime CurrentDateTime = _pharmacyContextLazy.Value.GetCurrentDateTime();
            List<LockingInfo> listLockInfo = _rMerchantLockLazy.Value.AsQueryable().Where(m => tableKeyValue == m.TableNameKeyValue)
                .ToList()
                .Where(
                    m => (TimeSpan.FromTicks(CurrentDateTime.Ticks).TotalSeconds - m.TimeLock) < GetLockingInterval()
                )
                .Select(m =>
                    new LockingInfo()
                    {
                        ComputerName = m.ComputerName,
                        UserName = m.UserUpdated,
                        TableName = m.TableName,
                        Key = m.KeyValue,
                        StartLockTime = CurrentDateTime.AddSeconds(-(TimeSpan.FromTicks(CurrentDateTime.Ticks).TotalSeconds - m.TimeLock)),
                        EndLockTime = CurrentDateTime.AddSeconds(-(TimeSpan.FromTicks(CurrentDateTime.Ticks).TotalSeconds - m.TimeLock)).AddSeconds(GetLockingInterval())
                    })
                .ToList();
            if (listLockInfo.Count() > 0)
            {
                LockingInfo lockInfo = listLockInfo[0];
                messageError = string.Format(SR.Instance.GetString(Constant.MSG_LOCKBYANOTHERUSER), SR.Instance.GetString(entity.GetType().FullName), entityInfo.Key, lockInfo.UserName, lockInfo.ComputerName);
            }
            return messageError;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        private bool IsMasterObject(EntityObject entity)
        {
            string entityNamespace = entity.GetType().Namespace;
            string merchantNamespace = typeof(Pharmacy.Data.Entity.LockTable).Namespace;
            if (entityNamespace == merchantNamespace)
            {
                return false;
            }
            else
            {
                throw new Exception("Something wrong !");
            }
        }
        private class EntityInfo
        {
            public string TableName;
            public string Key;
            public string TableNameKey;
        }
    }
    public class LockingInfo
    {
        public string UserName;
        public string ComputerName;
        public string TableName;
        public string Key;
        public DateTime StartLockTime;
        public DateTime EndLockTime;
    }
}
